package com.google.code.virtualhockey.vhx.shared.rpc;

import java.util.HashMap;
import java.util.Map;

import com.google.gwt.user.client.rpc.AsyncCallback;

/**
 * Dispatches RPC commands to a server.
 */
public class RpcCommandDispatcher
{
  private final IRpcCommandServiceAsync m_remoteService;
  private final ISecurityTokenProvider  m_securityTokenProvider;
  private final Map<String, IResponse>  m_mapCache;

  /**
   * Creates a new command dispatcher.
   *
   * @param service the service to be used to execute commands
   * @param securityTokenProvider the ISecurityTokenProvider to be used or
   *          <code>null</code> to not use security tokens
   */
  public RpcCommandDispatcher( IRpcCommandServiceAsync service, ISecurityTokenProvider securityTokenProvider )
  {
    m_remoteService = service;
    m_securityTokenProvider = securityTokenProvider;
    m_mapCache = new HashMap<String, IResponse>();
  }

  /**
   * Dispatches the given command directly.
   *
   * @param <S> the type of the dispatched command
   * @param <T> the type of the expected response
   * @param command the command to be dispatched
   * @param callback the callback that handles the command's response
   */
  @SuppressWarnings( "unchecked" )
  public <S extends ICommand<T>, T extends IResponse> void dispatch( S command, AbstractCommandCallback<T> callback )
  {
    m_remoteService.handle( getSecurityToken(), command, (AsyncCallback<IResponse>) callback );
  }

  /**
   * Checks if the command has already been executed before and returns a cached
   * response if possible. If no cached response is available, the command is
   * dispatched to the underlying handler service.
   *
   * @param <S> the type of the dispatched command
   * @param <T> the type of the expected response
   * @param command the command to be dispatched
   * @param callback the callback that handles the command's response
   */
  @SuppressWarnings( "unchecked" )
  public <S extends ICommand<T>, T extends IResponse> void dispatchWithCache( final S command,
      final AbstractCommandCallback<T> callback )
  {
    final String strCacheKey = getCommandCacheKey( command );
    T            response = (T) m_mapCache.get( strCacheKey );

    if ( response != null )
      callback.onSuccess( response );
    else
    {
      m_remoteService.handle( getSecurityToken(), command, new AbstractCommandCallback<IResponse>() {
        @Override
        public void onFailure( Throwable t )
        {
          callback.onFailure( t );
        }

        @Override
        public void handleAnswer( IResponse response )
        {
          m_mapCache.put( strCacheKey, response );
          callback.onSuccess( (T) response );
        }
      } );
    }
  }

  /**
   * Returns the key that should be used for caching command results.
   *
   * @param cmd the command for which to build a cache key
   * @return the command's cache key
   */
  private String getCommandCacheKey( ICommand<?> cmd )
  {
    if ( cmd.getDispatchInfo() == null )
      return cmd.getClass().getName();

    return cmd.getClass().getName() + "$$$" + cmd.getDispatchInfo();
  }

  /**
   * Returns the security token that should be used for the next request. If no
   * {@link ISecurityTokenProvider} is set, <code>null</code> is returned.
   */
  private String getSecurityToken()
  {
    if ( m_securityTokenProvider != null )
      return m_securityTokenProvider.getClientSecurityToken();

    return null;
  }
}
